package org.lc.oauth.extension.handler;

import ch.qos.logback.core.joran.util.beans.BeanUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.json.JSONUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.encoders.UTF8;
import org.lc.oauth.util.JwtUtil;
import org.lc.platform.base.constant.AuthConstant;
import org.lc.platform.base.constant.LcConstant;
import org.lc.platform.base.result.Result;
import org.lc.platform.base.enums.LogCategoryEnum;
import org.lc.platform.base.util.LoggerUtil;
import org.lc.platform.base.util.SmEncryptUtil;
import org.lc.platform.redis.service.CacheService;
import org.lc.service.system.client.feign.IUserFeign;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.endpoint.DefaultOAuth2AccessTokenResponseMapConverter;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import java.io.IOException;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 认证成功处理器
 *
 * @author dl
 * @since 3.0.0
 */
@Slf4j
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private final Converter<OAuth2AccessTokenResponse, Map<String, Object>> accessTokenResponseParametersConverter = new DefaultOAuth2AccessTokenResponseMapConverter();


    private final CacheService cacheService;

    private final JwtUtil jwtUtil;

    private final IUserFeign iUserFeign;

    public MyAuthenticationSuccessHandler(CacheService cacheService, JwtUtil jwtUtil, IUserFeign iUserFeign) {
        this.cacheService = cacheService;
        this.jwtUtil = jwtUtil;
        this.iUserFeign = iUserFeign;
    }

    /**
     * 自定义认证成功响应数据结构
     *
     * @param request        the request which caused the successful authentication
     * @param response       the response
     * @param authentication the <tt>Authentication</tt> object which was created during
     *                       the authentication process.
     * @throws IOException      if an I/O related error occurs during the response
     * @throws ServletException if a servlet-related error occurs during the response
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(200);
        var accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;
        var accessToken = accessTokenAuthentication.getAccessToken();
        var refreshToken = accessTokenAuthentication.getRefreshToken();

        var builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue()).tokenType(accessToken.getTokenType());

        if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
            builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
        }
        if (refreshToken != null) {
            builder.refreshToken(refreshToken.getTokenValue());
        }
        /* TODO 此处可以添加额外参数*/
        OAuth2AccessTokenResponse accessTokenResponse = builder.build();
        Map<String, Object> tokenResponseParameters = this.accessTokenResponseParametersConverter.convert(accessTokenResponse);

        /* TODO 缓存token做异地登录（可扩展）*/
        var userId = cacheTokenToSingle(accessTokenAuthentication);
        if (ObjUtil.isNotNull(userId)) {
            /* 通知网关进行日志记录 */
            response.addHeader(LcConstant.X_LOG_ID, LoggerUtil.createEncoderLogger(userId, LogCategoryEnum.LOGIN.getValue(), false));
        }
        String clientId = accessTokenAuthentication.getRegisteredClient().getClientId();
        if ("knife4j".equals(clientId)) {
            //  Knife4j测试客户端ID（Knife4j自动填充的 access_token 须原生返回，不能被包装成业务码数据格式）
            if (tokenResponseParameters != null) {
                response.getWriter().write(JSONUtil.toJsonStr(tokenResponseParameters));
            }
        } else {
            response.getWriter().write(JSONUtil.toJsonStr(Result.data(tokenResponseParameters)));
        }
    }


    private String cacheTokenToSingle(OAuth2AccessTokenAuthenticationToken accessTokenAuthentication) {
        Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();
        var userId = additionalParameters.get(AuthConstant.USER_ID);
        var token = accessTokenAuthentication.getAccessToken().getTokenValue();
        if (ObjUtil.isNull(userId)) {
            var user = jwtUtil.getJwtUser(token);
            userId = user.getUserId();
        }

        if (ObjUtil.isNotNull(userId)) {
//            var key = AuthConstant.CACHE_TOKEN + userId;
            var key = userId + ":jwt_token";
            /* TODO 此处可做最大登录此处暂时不做限制只能单地点登录 */
            cacheService.setList(key, List.of(token));

            /* 登录成功初始化错误次数 */
            var keyLocks = userId + ":account_lock";
            cacheService.remove(keyLocks);
            /* 缓存APIS 和 ACLS */
            var apisKey = userId + ":apis";
            var apis = (List<String>) additionalParameters.get(AuthConstant.APIS);
            if (ObjUtil.isNotNull(apis)) {
                cacheService.setList(apisKey, apis);
            }
            var aclsKey = userId + ":acls";
            var acls = (List<String>) additionalParameters.get(AuthConstant.ACLS);
            if (ObjUtil.isNotNull(acls)) {
                cacheService.setList(aclsKey, acls);
            }
            iUserFeign.setOnlineByUserId((String) userId, true);
        }
        return (String) userId;
    }
}
